home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programming Sound Cards
/
Programming Sound Cards.iso
/
sound_58
/
sb.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-01-01
|
19KB
|
643 lines
/*
* Play and record digitized sound sample on soundblaster DAC/ADC using DMA.
* This source code is in the public domain.
*
* Modification History
*
* 9-Nov-93 David Baggett Wrote it based on Sound Blaster
* <dmb@ai.mit.edu> Freedom project and Linux code.
*
* 24-Jun-94 Gerhard Kordmann - modified for recording facilities
* - added keyboard safety-routines to
* allow stopping of playing/recording
* - removed click while buffer switch
* - added bugfixes by Grzegorz Jablonski
* and several safety checks
* - added free dosmem at end of program
* <grzegorz@kmm-lx.p.lod.edu.pl>
* 08-Jul-94 Gerhard Kordmann - click also removed in recording
* - changes from dosmem... to memcpy
* 03-Sep-94 Gerhard Kordmann - added Highspeed DMA for frequencies
* above 37000 Hz (also in sb.h)
* - removed memcpy due to new dpmi-style
* of djgpp
* <kordmann@ldv01.Uni-Trier.de>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <pc.h>
#include "sb.h"
/* GO32 DPMI structs for accessing DOS memory. */
static _go32_dpmi_seginfo dosmem; /* DOS (conventional) memory buffer */
static _go32_dpmi_seginfo oldirq_rm; /* original real mode IRQ */
static _go32_dpmi_registers rm_regs;
static _go32_dpmi_seginfo rm_si; /* real mode interrupt segment info */
static _go32_dpmi_seginfo oldirq_pm; /* original prot-mode IRQ */
static _go32_dpmi_seginfo pm_si; /* prot-mode interrupt segment info */
/* Card parameters */
unsigned int sb_ioaddr;
unsigned int sb_irq;
unsigned int sb_dmachan;
/* Is a sound currently playing or recorded ? */
volatile int sb_dma_active = 0;
/* Conventional memory buffers for DMA. */
static volatile int sb_bufnum = 0;
static char *sb_buf[2];
static unsigned int sb_buflen[2];
/* Info about current sample */
static unsigned char *sb_curdata; /* pointer to next bit of data */
static unsigned long sb_curlength; /* total length length left to play */
static int HIGHSPEED; /* flag for normal/highspeed DMA */
/* DMA chunk size, in bytes.
* This parameter determines how big our DMA buffers are. We play
* the sample by piecing together chunks that are this big. This
* means that we don't have to copy the entire sample down into
* conventional memory before playing it. (A nice side effect of
* this is that we can play samples that are longer than 64K.)
*
* Setting this is tricky. If it's too small, we'll get lots
* of interrupts, and slower machines might not be able to keep
* up. Furthermore, the smaller this is, the more grainy the
* sound will come out.
*
* On the other hand, if we make it too big there will be a noticeable
* delay between a call to sb_play and when the sound actually starts
* playing, which is unacceptable for things like games where sound
* effects should be "instantaneous".
*
*/
#define DMA_CHUNK (16000)
/* Interrupt handler for recording
*
* This is called in both protected mode and in real mode -- this means
* we don't have to switch modes when we service the interrupt.
*/
void sb_intr_rec(_go32_dpmi_registers *reg)
{
register unsigned n = sb_bufnum; /* buffer we just recorded */
inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); /* Acknowledge soundblaster */
sb_rec_buffer(1 - n); /* Start next buffer recording */
sb_empty_buffer(n); /* Save this buffer */
outportb(0x20, 0x20); /* Acknowledge the interrupt */
enable();
}
/* Save buffer n in sb_curdata and calculate size for next time */
void sb_empty_buffer(register unsigned n)
{
if(sb_buflen[n] > 0) {
dosmemget((unsigned long) sb_buf[n], sb_buflen[n], sb_curdata);
sb_curdata += sb_buflen[n];
sb_curlength -= sb_buflen[n];
/* for determination of buffersize keep in mind, that right now */
/* the other buffer is recording up to DMA_CHUNK bytes. Only if */
/* - after saving this other buffer - there is still work to do,*/
/* then this buffer can take the rest in the next round */
if (sb_curlength > DMA_CHUNK) {
if (sb_curlength > 2*DMA_CHUNK)
sb_buflen[n] = DMA_CHUNK;
else
sb_buflen[n] = sb_curlength - DMA_CHUNK;
}
else
sb_buflen[n] = 0;
}
}
void sb_rec_buffer(register unsigned n)
{
int t;
unsigned char im, tm;
if (sb_buflen[n] == 0) { /* See if we're already done */
sb_dma_active = 0;
return;
}
disable();
im = inportb(0x21); /* Enable interrupts on PIC */
tm = ~(1 << sb_irq);
outportb(0x21,im & tm);
outportb(SB_DMA_MASK, 5); /* Set DMA mode to 'record' */
outportb(SB_DMA_FF, 0);
outportb(SB_DMA_MODE, 0x45);
sb_bufnum = n; /* Set transfer address */
t = (int) ((unsigned long) sb_buf[n] >> 16) ;
outportb(SB_DMAPAGE + 3, t & 0xFF);
t = (int) ((unsigned long) sb_buf[n] & 0xFFFF);
outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan, t >> 8);
/* Set transfer length byte count */
outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8);
outportb(SB_DMA_MASK, sb_dmachan); /* Unmask DMA channel */
enable();
if(HIGHSPEED) {
sb_writedac(SB_SET_BLOCKSIZE); /* prepare block programming */
}
else {
sb_writedac(SB_DMA_ADC); /* command byte for DMA ADC transfer */
}
sb_writedac((sb_buflen[n]-1) & 0xFF); /* sb_write length */
sb_writedac((sb_buflen[n]-1) >> 8);
if(HIGHSPEED)
sb_writedac(SB_HIGH_DMA_8_BIT_ADC); /* command byte for high speed DMA ADC transfer */
sb_dma_active = 1; /* A sound is recorded now. */
}
/* Record a sample through the ADC using DMA. */
/* return number of bytes actually recorded */
unsigned long sb_rec(unsigned char *data, unsigned long length)
{
sb_install_interrupts(sb_intr_rec); /* Install our interrupt handlers */
sb_curdata = data; /* Prime the buffers */
sb_curlength = length;
if(length > DMA_CHUNK)
sb_buflen[0] = DMA_CHUNK;
else
sb_buflen[0] = length;
if(length <= DMA_CHUNK)
sb_buflen[1] = 0;
else
if(length > 2*DMA_CHUNK)
sb_buflen[1] = DMA_CHUNK;
else
sb_buflen[1] = length-DMA_CHUNK;
sb_rec_buffer(0); /* Start the first buffer recording. */
while (sb_dma_active)
if(kbhit()) { /* kbhit crashed sometimes */
int rest;
rest = sb_read_counter(); /* samples still to record */
if(HIGHSPEED)
sb_reset(); /* writedac blocked in HS mode */
else
sb_writedac(SB_HALT_DMA); /* stop playing */
rest = sb_buflen[sb_bufnum] - rest;/* samples already recorded */
length -= (sb_curlength - rest);/* total samples recorded */
sb_buflen[sb_bufnum] = rest; /* save those samples */
sb_empty_buffer(sb_bufnum);
sb_dma_active = 0; /* and exit the loop */
sb_buflen[0] = sb_buflen[1] = sb_curlength = 0; /* clean up */
}
sb_cleanup_ints(); /* remove interrupts */
return length;
}
void sb_intr_play(_go32_dpmi_registers *reg)
{
register unsigned n = sb_bufnum; /* buffer we just played */
inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); /* Acknowledge soundblaster */
sb_play_buffer(1 - n); /* Start next buffer player */
sb_fill_buffer(n); /* Fill this buffer for next time around */
outportb(0x20, 0x20); /* Acknowledge the interrupt */
enable();
}
/* Fill buffer n with the next data. */
void sb_fill_buffer(register unsigned n)
{
if (sb_curlength > DMA_CHUNK) {
sb_buflen[n] = DMA_CHUNK;
dosmemput(sb_curdata, DMA_CHUNK, (unsigned long) sb_buf[n]);
sb_curlength -= DMA_CHUNK;
sb_curdata += DMA_CHUNK;
}
else if (sb_curlength <= 0) {
sb_buflen[n] = 0;
sb_curlength = 0;
}
else {
sb_buflen[n] = sb_curlength;
dosmemput(sb_curdata, sb_curlength, (unsigned long) sb_buf[n]);
sb_curdata += sb_curlength;
sb_curlength = 0;
}
}
void sb_play_buffer(register unsigned n)
{
int t;
unsigned char im, tm;
if (sb_buflen[n] <= 0) { /* See if we're already done */
sb_dma_active = 0;
return;
}
disable();
im = inportb(0x21); /* Enable interrupts on PIC */
tm = ~(1 << sb_irq);
outportb(0x21,im & tm);
outportb(SB_DMA_MASK, 5); /* Set DMA mode 'play' */
outportb(SB_DMA_FF, 0);
outportb(SB_DMA_MODE, 0x49);
sb_bufnum = n; /* Set transfer address */
t = (int) ((unsigned long) sb_buf[n] >> 16);
outportb(SB_DMAPAGE + 3, t);
t = (int) ((unsigned long) sb_buf[n] & 0xFFFF);
outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan, t >> 8);
/* Set transfer length byte count */
outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8);
outportb(SB_DMA_MASK, sb_dmachan); /* Unmask DMA channel */
enable();
if(HIGHSPEED) {
sb_writedac(SB_SET_BLOCKSIZE); /* prepare block programming */
}
else {
sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */
}
sb_writedac((sb_buflen[n]-1) & 0xFF); /* sb_write length */
sb_writedac((sb_buflen[n]-1) >> 8);
if(HIGHSPEED)
sb_writedac(SB_HIGH_DMA_8_BIT_DAC); /* command byte for high speed DMA DAC transfer */
sb_dma_active = 1; /* A sound is playing now. */
}
/* Play a sample through the DAC using DMA. */
void sb_play(unsigned char *data, unsigned long length)
{
sb_install_interrupts(sb_intr_play); /* Install our interrupt handlers */
sb_curdata = data; /* Prime the buffers */
sb_curlength = length;
sb_fill_buffer(0);
sb_fill_buffer(1);
sb_play_buffer(0); /* Start the first buffer playing. */
while (sb_dma_active) { /* keyboard overflow crashes */
if(kbhit()) { /* so did kbhit() */
if(getch() == 27) {
if(HIGHSPEED)
sb_reset(); /* writedac blocked in HS mode */
else
sb_writedac(SB_HALT_DMA);
sb_dma_active = 0;
sb_buflen[0] = sb_buflen[1] = sb_curlength = 0;
}
else
kbclear();
}
}
sb_cleanup_ints(); /* remove interrupts */
}
/* Set sampling/playback rate.
* Parameter is rate in Hz (samples per second).
*/
void sb_set_sample_rate(unsigned int rate)
{
unsigned char tc;
if(rate > 37000) HIGHSPEED = 1;
else HIGHSPEED = 0;
if(HIGHSPEED)
tc = (unsigned char) ((65536 - 256000000/rate) >> 8);
else
tc = (unsigned char) (256 - 1000000/rate);
sb_writedac(SB_TIME_CONSTANT); /* Command byte for sample rate */
sb_writedac(tc); /* Sample rate time constant */
}
void sb_voice(int state)
{
sb_writedac(state ? SB_SPEAKER_ON : SB_SPEAKER_OFF);
}
/* Read soundblaster card parameters from BLASTER enivronment variable .*/
void sb_getparams()
{
char *t, *blaster;
sb_ioaddr = 0x220; /* Set arguments to Soundblaster defaults */
sb_irq = 7;
sb_dmachan = 1;
t = getenv("BLASTER");
if (!t)
return;
blaster = strdup(t); /* Get a copy */
t = strtok(blaster, " \t"); /* Parse the BLASTER variable */
while (t) {
switch (t[0]) {
case 'A':
case 'a':
/* I/O address */
sscanf(t + 1, "%x", &sb_ioaddr);
break;
case 'I':
case 'i':
/* IRQ */
sb_irq = atoi(t + 1);
break;
case 'D':
case 'd':
/* DMA channel */
sb_dmachan = atoi(t + 1);
break;
case 'T':
case 't':
/* what is this? */
break;
default:
printf("Unknown BLASTER option %c\n",t[0]);
break;
}
t = strtok(NULL," \t");
}
free(blaster);
return;
}
/* Init the soundblaster card. */
/* gk : modified for autodetection */
int sb_initcard(void)
{
int i,j,error=0;
int NrOfBases = 6;
int Bases[] = {0x210,0x220,0x230,0x240,0x250,0x260};
int Base;
for(i=0;i<NrOfBases && error == 0;i++) {
Base = Bases[i];
outportb(Base + SB_DSP_RESET,1);
inportb(Base + SB_DSP_RESET); /* Kill some time */
inportb(Base + SB_DSP_RESET);
inportb(Base + SB_DSP_RESET);
inportb(Base + SB_DSP_RESET);
outportb(Base + SB_DSP_RESET, 0);
for(j=0;j<100;j++) {
if(sb_read_dac(Base) == 0xAA) {
error = 1;
break;
}
}
}
if(error == 0)
Base = 0;
return(Base);
}
int sb_read_dac(int Base)
{
int i;
for(i=0;i<10000;i++) {
if(inportb(Base + SB_DSP_DATA_AVAIL) & 0x080)
break;
}
return(inportb(Base+0x0A));
}
void sb_install_interrupts(void (*sb_intr)(_go32_dpmi_registers *))
{
sb_install_rm_interrupt(sb_intr);
sb_install_pm_interrupt(sb_intr);
}
/*
* Install our interrupt as the real mode interrupt handler for
* the IRQ the soundblaster is on.
*
* We accomplish this by have GO32 allocate a real mode callback for us.
* The callback packages our protected mode code up in a real mode wrapper.
*/
void sb_install_rm_interrupt(void (*sb_intr)(_go32_dpmi_registers *))
{
int ret;
rm_si.pm_offset = (int) sb_intr;
ret = _go32_dpmi_allocate_real_mode_callback_iret(&rm_si, &rm_regs);
if (ret != 0) {
printf("cannot allocate real mode callback, error=%04x\n",ret);
exit(1);
}
disable();
_go32_dpmi_get_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &rm_si);
enable();
}
/* Remove our real mode interrupt handler. */
void sb_cleanup_rm_interrupt()
{
disable();
_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
/* gk : added safety check */
if(rm_si.size != -1)
_go32_dpmi_free_real_mode_callback(&rm_si);
rm_si.size = -1;
enable();
}
/* Install our interrupt as the protected mode interrupt handler for
* the IRQ the soundblaster is on. */
void sb_install_pm_interrupt(void (*sb_intr)(_go32_dpmi_registers *))
{
int ret;
disable();
pm_si.pm_offset = (int) sb_intr;
/* changes to wrap by grzegorz */
ret = _go32_dpmi_allocate_iret_wrapper(&pm_si);
if (ret != 0) {
printf("cannot allocate protected mode wrapper, error=%04x\n",ret);
exit(1);
}
pm_si.pm_selector = _go32_my_cs();
_go32_dpmi_get_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
_go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &pm_si);
enable();
}
/* Remove our protected mode interrupt handler. */
void sb_cleanup_pm_interrupt()
{
disable();
/* changes to wrap by grzegorz, safety chek by gk */
if(pm_si.size != -1)
_go32_dpmi_free_iret_wrapper(&pm_si);
pm_si.size = -1;
_go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
enable();
}
/* Allocate conventional memory for our DMA buffers.
* Each DMA buffer must be aligned on a 64K boundary in physical memory. */
int sb_init_buffers()
{
dosmem.size = 65536*3/16;
if (_go32_dpmi_allocate_dos_memory(&dosmem)) {
printf("Unable to allocate dos memory - max size is %lu\n", dosmem.size);
dosmem.size = -1;
return(0);
}
(unsigned long) sb_buf[0] = dosmem.rm_segment * 16;
(unsigned long) sb_buf[0] += 0x0FFFFL;
(unsigned long) sb_buf[0] &= 0xFFFF0000L;
(unsigned long) sb_buf[1] = (unsigned long) sb_buf[0] + 0x10000;
return(1);
}
/* Initliaze our internal buffers and the card itself to prepare
* for sample playing.
*
* Call this once per program, not once per sample. */
int sb_init()
{
memset(&rm_regs,0,sizeof(_go32_dpmi_registers));
/* undefined registers cause trouble */
rm_si.size = -1; /* to allow safety check before free */
pm_si.size = -1;
dosmem.size = -1;
sb_getparams(); /* Card card params and initialize card. */
sb_ioaddr = sb_initcard();
if(sb_ioaddr)
/* Allocate buffers in conventional memory for double-buffering */
if(!sb_init_buffers())
return 0;
return(sb_ioaddr);
}
/* Remove interrupt handlers */
void sb_cleanup_ints()
{
/* Remove our interrupt handlers */
sb_cleanup_rm_interrupt();
sb_cleanup_pm_interrupt();
}
/* leave no traces on exiting module
* Call this at end of program or if you won't need sb functions anymore
*/
int sb_cleanup()
{
if(dosmem.size == -1) /* There is nothing to free */
return(1);
if (!_go32_dpmi_free_dos_memory(&dosmem)) {
printf("Unable to free dos memory");
return(0);
}
return(1);
}
int sb_read_counter(void)
/* tells you how many bytes DMA play/recording have still to be done */
{
outportb(SB_DMA_FF,0);
return(inportb(SB_DMA + 2 * sb_dmachan + 1) +
256*inportb(SB_DMA + 2 * sb_dmachan + 1));
}
void sb_reset(void)
{
int j;
outportb(sb_ioaddr + SB_DSP_RESET,1);
inportb(sb_ioaddr + SB_DSP_RESET); /* Kill some time */
inportb(sb_ioaddr + SB_DSP_RESET);
inportb(sb_ioaddr + SB_DSP_RESET);
inportb(sb_ioaddr + SB_DSP_RESET);
outportb(sb_ioaddr + SB_DSP_RESET, 0);
for(j=0;j<100;j++) {
if(sb_read_dac(sb_ioaddr) == 0xAA) {
break;
}
}
}
void kbclear(void)
{
short buffer;
dosmemget(0x41a,sizeof(short),&buffer);
dosmemput(&buffer,2,0x41c);
}
/*
* Convenient wrappers for the sound functions
*/
void SoundPlay(int Rate, char *data, unsigned long length)
{
sb_voice(1);
sb_set_sample_rate(Rate);
sb_play(data, length);
sb_voice(0);
}
unsigned long SoundRec(int Rate, char *data, unsigned long length)
{
sb_set_sample_rate(Rate);
return(sb_rec(data, length));
}